package com.senyint.sys.utils.net.ftp;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;

import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPConnectionClosedException;
import org.apache.commons.net.ftp.FTPHTTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;
import org.apache.commons.net.io.CopyStreamEvent;
import org.apache.commons.net.io.CopyStreamListener;
import org.apache.commons.net.util.TrustManagerUtils;

import com.senyint.sys.utils.FilePathUtils;
import com.senyint.sys.utils.log.Log;

public class FtpClient {

	private FTPClient ftp;
	private String serverHost;
	private int serverPort = 0;
	private String username;
	private String password;

	private String protocol;

	private String proxyHost;
	private int proxyPort;
	private String proxyUser;
	private String proxyPassword;

	private String trustmgr;
	private boolean printHash = false;
	private int keepAliveTimeout;
	private int controlKeepAliveReplyTimeout;
	private boolean hidden = false;
	private boolean binaryTransfer = true;
	private boolean localActive = false;// 主动模式/被动模式
	private boolean useEpsvWithIPv4 = false;

	public FtpClient(String serverHost, int serverPort, String username, String password) {
		this.serverHost = serverHost;
		this.serverPort = serverPort;
		this.username = username;
		this.password = password;
		this.init();
	}

	public FtpClient(String serverHost, int serverPort, String username, String password, String protocol, String proxyHost, int proxyPort, String proxyUser, String proxyPassword, String trustmgr, boolean printHash, int keepAliveTimeout, int controlKeepAliveReplyTimeout, boolean hidden, boolean binaryTransfer, boolean localActive, boolean useEpsvWithIPv4) {
		this.serverHost = serverHost;
		this.serverPort = serverPort;
		this.username = username;
		this.password = password;
		this.protocol = protocol;

		this.proxyHost = proxyHost;
		this.proxyPort = proxyPort;
		this.proxyUser = proxyUser;
		this.proxyPassword = proxyPassword;

		this.trustmgr = trustmgr;
		this.printHash = printHash;
		this.keepAliveTimeout = keepAliveTimeout;
		this.controlKeepAliveReplyTimeout = controlKeepAliveReplyTimeout;
		this.hidden = hidden;
		this.binaryTransfer = binaryTransfer;
		this.localActive = localActive;
		this.useEpsvWithIPv4 = useEpsvWithIPv4;
		this.init();
	}

	private void init() {
		if (protocol == null) {
			if (proxyHost != null) {
				Log.INSTANCE.debug("Using HTTP proxy server: {}", proxyHost);
				ftp = new FTPHTTPClient(proxyHost, proxyPort, proxyUser, proxyPassword);
			} else {
				ftp = new FTPClient();
			}
		} else {
			FTPSClient ftps;
			if (protocol.equals("true")) {
				ftps = new FTPSClient(true);
			} else if (protocol.equals("false")) {
				ftps = new FTPSClient(false);
			} else {
				String prot[] = protocol.split(",");
				if (prot.length == 1) { // Just protocol
					ftps = new FTPSClient(protocol);
				} else { // protocol,true|false
					ftps = new FTPSClient(prot[0], Boolean.parseBoolean(prot[1]));
				}
			}
			ftp = ftps;
			if ("all".equals(trustmgr)) {
				ftps.setTrustManager(TrustManagerUtils.getAcceptAllTrustManager());
			} else if ("valid".equals(trustmgr)) {
				ftps.setTrustManager(TrustManagerUtils.getValidateServerCertificateTrustManager());
			} else if ("none".equals(trustmgr)) {
				ftps.setTrustManager(null);
			}
		}

		if (printHash) {
			ftp.setCopyStreamListener(createListener());
		}
		if (keepAliveTimeout >= 0) {
			ftp.setControlKeepAliveTimeout(keepAliveTimeout);
		}
		if (controlKeepAliveReplyTimeout >= 0) {
			ftp.setControlKeepAliveReplyTimeout(controlKeepAliveReplyTimeout);
		}
		ftp.setListHiddenFiles(hidden);

		// suppress login details
		// 设置将过程中使用到的命令输出到控制台
		ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));

	}

	private void connectFtpServer() {
		try {
			int reply;
			if (this.serverPort > 0) {
				ftp.connect(this.serverHost, this.serverPort);
			} else {
				ftp.connect(this.serverHost);
			}
			Log.INSTANCE.debug("Connected to {} on {}", this.serverHost, (this.serverPort > 0 ? this.serverPort : ftp.getDefaultPort()));

			// After connection attempt, you should check the reply code to
			// verify
			// success.
			reply = ftp.getReplyCode();

			if (!FTPReply.isPositiveCompletion(reply)) {
				ftp.disconnect();
				Log.INSTANCE.error("FTP server refused connection.");
			}
		} catch (IOException e) {
			if (ftp.isConnected()) {
				try {
					ftp.disconnect();
				} catch (IOException f) {
					// do nothing
				}
			}
			Log.INSTANCE.error("Could not connect to server.");
			e.printStackTrace();
		}

		__main: try {
			if (!ftp.login(this.username, this.password)) {
				ftp.logout();
				break __main;
			}

			Log.INSTANCE.debug("Remote system is {}", ftp.getSystemType());

			if (binaryTransfer) {
				ftp.setFileType(FTP.BINARY_FILE_TYPE);
			} else {
				// in theory this should not be necessary as servers should
				// default to ASCII
				// but they don't all do so - see NET-500
				ftp.setFileType(FTP.ASCII_FILE_TYPE);
			}

			// Use passive mode as default because most of us are
			// behind firewalls these days.
			if (localActive) {
				ftp.enterLocalActiveMode();
			} else {
				ftp.enterLocalPassiveMode();
			}

			ftp.setUseEPSVwithIPv4(useEpsvWithIPv4);
			ftp.changeWorkingDirectory("/");
		} catch (FTPConnectionClosedException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public void disConnect() {
		try {
			ftp.noop();
			ftp.logout();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (ftp.isConnected()) {
				try {
					ftp.disconnect();
				} catch (IOException f) {
					// do nothing
				}
			}
		}
	}

	public boolean upload(String file, String ftpDir, String ftpFileName) {
		return this.upload(new File(file), ftpDir, ftpFileName);
	}

	public boolean upload(File file, String ftpDir, String ftpFileName) {
		Log.INSTANCE.debug("upload file:{} to ftp", file.getAbsolutePath());
		boolean r = false;
		if (file.exists()) {
			try {
				FileInputStream fileInputStream = new FileInputStream(file);
				r = this.upload(fileInputStream, ftpDir, ftpFileName);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		}
		return r;
	}

	public boolean upload(InputStream inputStream, String ftpDir, String ftpFileName) {
		boolean r = false;
		Log.INSTANCE.debug("upload ftp file:{}{}", ftpDir, ftpFileName);
		if (this.ftp.isConnected()) {
			boolean cd = this.cd(clearPathSeparator(ftpDir));
			if (cd) {
				try {
					r = this.ftp.storeFile(ftpFileName, inputStream);
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					if (inputStream != null) {
						try {
							inputStream.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}
		} else {
			this.connectFtpServer();
			r = this.upload(inputStream, ftpDir, ftpFileName);
		}
		return r;
	}

	/**
	 * @Title: download
	 * @Description: 下载ftp文件
	 *               <P>
	 *               关闭防火墙!<br>
	 *               关闭防火墙!关闭防火墙!!<br>
	 *               关闭防火墙!关闭防火墙!!关闭防火墙!!!<br>
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年4月10日 下午4:10:13
	 * @version 1.0
	 * @param ftpDir
	 * @param ftpFileName
	 * @return
	 */
	public InputStream download(String ftpDir, String ftpFileName) {
		return download(FilePathUtils.append(ftpDir, ftpFileName).toString());
	}

	/**
	 * @Title: download
	 * @Description: 下载ftp文件
	 *               <P>
	 *               关闭防火墙!<br>
	 *               关闭防火墙!关闭防火墙!!<br>
	 *               关闭防火墙!关闭防火墙!!关闭防火墙!!!<br>
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年4月10日 下午4:10:01
	 * @version 1.0
	 * @param ftpFilePath
	 * @return
	 */
	public InputStream download(String ftpFilePath) {
		Log.INSTANCE.debug("ftp file:{}", ftpFilePath);
		InputStream r = null;
		if (this.ftp.isConnected()) {
			try {
				r = ftp.retrieveFileStream(ftpFilePath);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			this.connectFtpServer();
			r = this.download(ftpFilePath);
		}
		return r;
	}

	/**
	 * @Title: download
	 * @Description: 下载ftp文件
	 *               <P>
	 *               关闭防火墙!<br>
	 *               关闭防火墙!关闭防火墙!!<br>
	 *               关闭防火墙!关闭防火墙!!关闭防火墙!!!<br>
	 * @author WangNaiFei,WangNaiFei@senyint.com
	 * @date 2018年4月10日 下午4:08:23
	 * @version 1.0
	 * @param ftpFilePath
	 * @param local
	 * @return
	 */
	public boolean download(String ftpFilePath, OutputStream local) {
		Log.INSTANCE.debug("ftp file:{}", ftpFilePath);
		boolean r = false;
		if (this.ftp.isConnected()) {
			try {
				r = ftp.retrieveFile(ftpFilePath, local);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			this.connectFtpServer();
			r = this.download(ftpFilePath, local);
		}
		return r;
	}

	private boolean createDirectory(String ftpDir) throws IOException {
		boolean r = false;
		if (this.ftp.isConnected()) {
			r = this.ftp.changeWorkingDirectory(ftpDir);
			if (!r) {
				boolean makeDirectory = this.ftp.makeDirectory(ftpDir);
				if (makeDirectory) {
					r = this.ftp.changeWorkingDirectory(ftpDir);
					if (!r) {
						Log.INSTANCE.error("Can not cd dir '{}' on ftp '{}'", ftpDir, this.serverHost);
					}
				} else {
					Log.INSTANCE.error("Can not make dir '{}' on ftp '{}'", ftpDir, this.serverHost);
				}
			}
		} else {
			this.connectFtpServer();
			r = this.createDirectory(ftpDir);
		}
		return r;
	}

	private boolean cd(String path) {
		boolean r = false;
		if (this.ftp.isConnected()) {
			try {
				this.ftp.changeWorkingDirectory("/");
				String[] dirs = path.split("/");
				for (String dir : dirs) {
					r = this.createDirectory(dir);
					if (!r) {
						break;
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			this.connectFtpServer();
			r = this.cd(path);
		}
		return r;
	}

	private static String clearPathSeparator(String path) {
		if (path.startsWith(File.separator) || path.startsWith("/")) {
			path = path.substring(1);
		}
		if (path.endsWith(File.separator) || path.endsWith("/")) {
			path = path.substring(0, path.length() - 1);
		}
		return path.replace(File.separator, "/");
	}

	private static CopyStreamListener createListener() {
		return new CopyStreamListener() {
			private long megsTotal = 0;

			public void bytesTransferred(CopyStreamEvent event) {
				bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize());
			}

			public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize) {
				long megs = totalBytesTransferred / 1000000;
				for (long l = megsTotal; l < megs; l++) {
					Log.INSTANCE.error("#");
				}
				megsTotal = megs;
			}
		};
	}
}
